<?php

/**
 * Class IFileCache
 * 文件缓存类
 */
class IFileCache
{
    protected $cachePath    = 'runtime/cache';  //默认文件缓存存放路径
    protected $cachePrefix  = 'movie';          //文件缓存的key前缀
    protected $enableFileNameFormat = true;     //是否格式化文件名
    protected $cacheExt     = '.data';          //默认文件缓存扩展名
    protected $folderLevel  = 1;                //目录层级,基于$cachePath之下的

    /**
     * IFileCache constructor.
     * @param string $cachePath
     * @param string $cachePrefix
     * @param bool $enableFileNameFormat
     * @param string $cacheExt
     */
    public function __construct($cachePath = '', $cachePrefix = 'movie', $enableFileNameFormat = true, $cacheExt = '.data')
    {
        $this->cachePath    = empty($cachePath) ? $this->cachePath : $cachePath;
        $this->cachePrefix  = $cachePrefix;
        $this->enableFileNameFormat  = $enableFileNameFormat;
        $this->cacheExt     = $cacheExt;
    }

    /**
     * @brief  写入缓存
     * @param string $key     缓存的唯一key值
     * @param  mixed  $data    要写入的缓存数据
     * @param int $expire  缓存数据失效时间,单位：秒
     * @return bool   true:成功;false:失败;
     */
    public function set(string $key, $data, int $expire = 600)
    {
        if (empty($key)) {
            return null;
        }

        $fileName = $this->createCacheFilePath($key);

        $expireStr = sprintf('%010d', $expire + time());
        if (strlen($expireStr) > 10) {
            return false;
        }

        if (!file_exists($fileName)) {
            touch($fileName);
            chmod($fileName, 0777);
        }

        $writeLen = file_put_contents($fileName, $expireStr . serialize($data));
        if ($writeLen == 0) {
            return false;
        }
        touch($fileName, $expireStr);

        return true;
    }

    /**
     * @brief  读取缓存
     * @param string $key 缓存的唯一key值,当要返回多个值时可以写成数组
     * @return mixed  读取出的缓存数据;null:没有取到数据;false:缓存已经过期了;
     */
    public function get(string $key)
    {
        $fileName = $this->createCacheFilePath($key);
        if (is_file($fileName)) {
            $expireTime = file_get_contents($fileName, false, null, 0, 10);
            if ($expireTime > time()) {
                return @unserialize(file_get_contents($fileName, false, null, 10));
            } else {
                @unlink($fileName);
                return false;
            }
        }

        return null;
    }

    /**
     * @brief  删除缓存
     * @param string $key     缓存的唯一key值
     * @return bool   true:成功; false:失败;
     */
    public function delete(string $key): bool
    {
        if ($key === null || $key === '') {
            return false;
        }

        $fileName = $this->createCacheFilePath($key);
        if (is_file($fileName)) {
            return @unlink($fileName);
        }
        return true;
    }

    /**
     * @brief  拉取缓存，拉取后同时删除缓存
     * @param string $key     缓存的唯一key值
     * @return mixed|null 缓存不存在时返回NULL
     */
    public function pull($key)
    {
        $value = $this->get($key);
        $this->delete($key);
        return $value;
    }

    /**
     * @brief  根据key值计算缓存文件名
     * @param string $key 缓存的唯一key值
     * @return string 缓存文件路径
     */
    public function createCacheFilePath(string $key): string
    {
        $cacheDir = rtrim($this->cachePath, '\\/');
        if ($this->folderLevel > 0) {
            $hash      = abs(crc32($key));
            $cacheDir .= DIRECTORY_SEPARATOR . ($hash % 1024);  // 拆分成1024个文件缓存目录
            for ($i = 1; $i < $this->folderLevel; ++$i) {
                if (($folder = substr($hash, $i, 2)) !== false) {
                    $cacheDir .= DIRECTORY_SEPARATOR . $folder;
                }
            }
        }
        if (!is_dir($cacheDir)) {
            mkdir($cacheDir, 0777, true);
        }
        // 避免撞key，增强唯一性
        $filename = $this->enableFileNameFormat ?
            sprintf('%s_%s_%s_%s'.$this->cacheExt, $this->cachePrefix, strlen($this->cachePrefix), md5($key), strlen($key))
            : $this->cachePrefix.$key.$this->cacheExt;

        return $cacheDir.DIRECTORY_SEPARATOR.$filename;
    }
}
